
import EventPackage.Event;
import EventPackage.EventManagerInterface;
import EventPackage.EventQueue;
import java.util.ArrayList;
import java.util.List;

/**
 * Abstract event processor used as basis for system controllers and monitors
 *
 * @author Kyle Benedict
 */
public abstract class EventProcessor {

    protected String eventManagerIPAddress;               // Event Manager IP address
    protected EventManagerInterface eventManager = null;  // Interface object to the event manager
    protected boolean running = true;                    // State of the processor, whether it is running or not
    protected List<EventType> registeredEventTypes = new ArrayList<EventType>();
    protected long delayBetweenEventsMillis;
    protected boolean registered = false;

    /**
     * Constructor for the event processor.  Takes the event manager IP address
     * as a parameter and registers the controller with the event manager
     * hosted at that IP address.  Also takes an input parameter that defines
     * the delay in ms between event handling.
     * @param eventManagerIPAddress
     * @param delayBetweenEventsMillis
     */
    public EventProcessor(String eventManagerIPAddress, long delayBetweenEventsMillis) {
        this.delayBetweenEventsMillis = delayBetweenEventsMillis;
        this.eventManagerIPAddress = eventManagerIPAddress;
        register();
        if (getEventTypesToRegister() != null) {
            for (EventType et : getEventTypesToRegister()) {
                registerForEvent(et);
            }
        }
    }

    /**
     * Constructor for the event processor.  Assumes the event manager runs
     * at the localhost and takes an input parameter that defines the delay
     * in ms between event handling.
     * @param delayBetweenEventsMillis
     */
    public EventProcessor(long delayBetweenEventsMillis) {
        this(null, delayBetweenEventsMillis);
    }

    protected abstract EventType[] getEventTypesToRegister();

    /**
     * Registers for the parameter event type
     * @param eventType
     */
    protected void registerForEvent(EventType eventType) {
        registeredEventTypes.add(eventType);
    }

    public EventManagerInterface getEventManager() {
        return eventManager;
    }

    public String getEventManagerIPAddress() {
        return eventManagerIPAddress;
    }

    /**
     * Registers the controller with the host at the IP address at the
     * first argument in the parameter String array, or at localhost if the
     * String array is empty.
     * @param args
     */
    protected void register() {
        if (eventManagerIPAddress == null || eventManagerIPAddress.length() == 0) {
            // event manager is on the local system
            System.out.println("Attempting to register event processor on the local machine...");
            try {
                // Here we create an event manager interface object. This assumes
                // that the event manager is on the local machine
                eventManager = new EventManagerInterface();
            } catch (Exception e) {
                System.out.println(String.format("Error instantiating event manager interface: ", e));
                e.printStackTrace();
            }
        } else {
            // event manager is not on the local system
            System.out.println(String.format("Attempting to register event processor on the host %s", eventManagerIPAddress));
            try {
                // Here we create an event manager interface object. This assumes
                // that the event manager is NOT on the local machine
                eventManager = new EventManagerInterface(eventManagerIPAddress);
            } catch (Exception e) {
                System.out.println(String.format("Error instantiating event manager interface: ", e));
                e.printStackTrace();
            }
        }
        registered = true;
        onRegistered();
    }

    protected void unregister() {
        onUnregistered();
        if (eventManager != null) {
            try {
                eventManager.UnRegister();
            } catch (Exception e) {
                // Bury an exception that may be thrown when unregistering
            }
        }
        registered = false;
    }

    public boolean isRegistered() {
        return registered;
    }

    private void confirmMessage(Event event) {
        // Here we create the event.
        Event confirmationEvent = new Event(-1 * event.GetEventId(), event.GetMessage());
        // Here we send the event to the event manager.
        try {
            eventManager.SendEvent(confirmationEvent);
        } catch (Exception e) {
            System.out.println(String.format("Error Confirming Message: ", e));
            e.printStackTrace();
        }
    }

    protected final void processEvents() throws Exception {
        while (isRegistered() && running) {

            EventQueue eventQueue = null;
            try {
                eventQueue = eventManager.GetEventQueue();
            } // try
            catch (Exception e) {
                System.out.println(String.format("Error retrieving event queue: ", e));
                e.printStackTrace();
                throw e;
            }

            if (eventQueue == null) {
                String error = String.format("Retrieved a null event queue from the event manager at: %s",
                        (eventManagerIPAddress == null ? "localhost" : eventManagerIPAddress));
                System.out.println(error);
                throw new Exception(error);
            }

            // On processing allows for custom continuous logic to be added
            // to implementing classes
            onProcessing();

            int queueLength = eventQueue.GetSize();
            for (int i = 0; i < queueLength; i++) {
                // Remove the event from the event queue
                Event event = eventQueue.GetEvent();
                if (event != null) {
                    if (registeredEventTypes != null) {
                        for (EventType eventType : registeredEventTypes) {
                            if (eventType.getEventId() == event.GetEventId()) {
                                onEvent(event);
                                confirmMessage(event);
                                break;
                            }
                        }
                    }

                    if (EventType.TERMINATE.getEventId() == event.GetEventId()) {
                        unregister();
                        running = false;
                        break;
                    }
                }
            }

            try {
                Thread.sleep(delayBetweenEventsMillis);
            } catch (InterruptedException e) {
            }
        }
    }

    protected void sendEvent(Event event) {
        try {
            getEventManager().SendEvent(event);
        }
        catch (Exception e) {
            System.out.println(String.format("Error sending event: ", e));
        }
    }

    protected void onProcessing(){}
    protected abstract void onEvent(Event event);
    protected abstract void onRegistered();
    protected abstract void onUnregistered();
}
